////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   SoundProxy.cpp
//  Version:     v1.00
//  Created:     26/9/2004 by Timur.
//  Compilers:   Visual Studio.NET
//  Description: 
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "SoundProxy.h"
#include <ICryAnimation.h>
#include <IReverbManager.h>
#include "Entity.h"

#define TOO_MANY_SOUNDS 25
//#define DEBUG_SOUNDPROXY

#ifdef DEBUG_SOUNDPROXY
	#include <IRenderer.h>
#endif // DEBUG_SOUNDPROXY

#ifdef WIN64
	#pragma warning ( disable : 4244 )
#endif

//////////////////////////////////////////////////////////////////////////
CSoundProxy::CSoundProxy( CEntity *pEntity )
{
	m_pEntity = pEntity;

	Reset();
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::Reload( IEntity *pEntity,SEntitySpawnParams &params )
{
	m_pEntity = (CEntity*)pEntity;

	StopAllSounds();
	Reset();

	UpdateSounds();
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::Reset()
{
	m_bHide            = m_pEntity->IsHidden();
	m_nFlags           = 0;
	m_fEffectRadius    = 0.0f;
	m_currentLipSyncId = INVALID_SOUNDID;
	m_sTailname        = SOUND_OUTDOOR_TAILNAME;
	m_nBoneHead        = -1;
	m_nAttachmentIndex = -1;

	m_vLastVisibilityPos.zero();
	stl::free_container(m_SoundsAttached);
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::Release()
{
	StopAllSounds();
	m_pEntity = NULL;
	delete this;
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::UpdateSounds()
{
	OnMove();
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::PrecacheHeadBone()
{
	// checking for and caching HeadBone
	if (m_nBoneHead == -1 && m_nAttachmentIndex == -1)
	{
		if (ICharacterInstance* const pCharacter = m_pEntity->GetCharacter(0))
		{
			if (IAttachmentManager const* const pAttachmentManager = pCharacter->GetIAttachmentManager())
			{
				m_nAttachmentIndex = pAttachmentManager->GetIndexByName("voice");

				if (m_nAttachmentIndex == -1)
				{
					// There's no attachment so let's try to find the head bone.
					if (ISkeletonPose const* const pSkeletonPose = pCharacter->GetISkeletonPose())
					{
						m_nBoneHead = pSkeletonPose->GetJointIDByName("Bip01 Head");

						if (m_nBoneHead == -1)
						{
							// Has it been named differently?
							m_nBoneHead = pSkeletonPose->GetJointIDByName("def_head");
						}
					}
				}
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::SetSoundPosition(ISound *const pSound, Vec3 const* const __restrict vOffset, Vec3 const* const __restrict vDirection, Matrix34 const& tm)
{
	// Only set position on non-self-moving sounds
	if ((pSound->GetFlags() & FLAG_SOUND_SELFMOVING) == 0)
	{
		Matrix34 tmHead = tm;

		if (!vOffset || !vDirection)
		{
			if ((pSound->GetFlags() & FLAG_SOUND_VOICE) != 0)
			{
				if (ICharacterInstance* const pCharacter = m_pEntity->GetCharacter(0))
				{
					if (m_nAttachmentIndex != -1)
					{
						if (IAttachmentManager const* const pAttachmentManager = pCharacter->GetIAttachmentManager())
						{
							if (IAttachment const* const pAttachment = pAttachmentManager->GetInterfaceByIndex(m_nAttachmentIndex))
							{
								tmHead = Matrix34(pAttachment->GetAttWorldAbsolute());
							}
						}
					}
					else if (m_nBoneHead != -1)
					{
						// re-query SkeletonPose to prevent crash on removed Character
						if (ISkeletonPose* const pSkeletonPose = pCharacter->GetISkeletonPose())
						{
							tmHead = tm * Matrix34(pSkeletonPose->GetAbsJointByID(m_nBoneHead));
						}
					}
				}

				pSound->SetPosition(tmHead.GetTranslation());
				pSound->SetMatrix(tmHead);
			}
			else
			{
				pSound->SetPosition(tm.GetTranslation());
				pSound->SetMatrix(tm);
			}
		}
		else
		{
			Matrix34 tmOffs;
			tmOffs.SetIdentity();

			if ((pSound->GetFlags() & FLAG_SOUND_VOICE) != 0)
			{
				if (ICharacterInstance* const pCharacter = m_pEntity->GetCharacter(0))
				{
					if (m_nAttachmentIndex != -1)
					{
						if (IAttachmentManager const* const pAttachmentManager = pCharacter->GetIAttachmentManager())
						{
							if (IAttachment const* const pAttachment = pAttachmentManager->GetInterfaceByIndex(m_nAttachmentIndex))
							{
								tmHead = Matrix34(pAttachment->GetAttWorldAbsolute());
							}
						}
					}
					else if (m_nBoneHead != -1)
					{
						// re-query SkeletonPose to prevent crash on removed Character
						if (ISkeletonPose* const pSkeletonPose = pCharacter->GetISkeletonPose())
						{
							tmHead = tm * Matrix34(pSkeletonPose->GetAbsJointByID(m_nBoneHead));
						}
					}
				}				

				tmOffs = Matrix33::CreateRotationVDir(*vDirection);
				tmOffs.SetTranslation(*vOffset);
				tmOffs = tmHead * tmOffs;
			}
			else
			{
				tmOffs.SetTranslation(*vOffset);
				tmOffs = tm * tmOffs;
			}

			pSound->SetPosition(tmOffs.GetTranslation());
			pSound->SetMatrix(tmOffs);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::OnMove()
{
	const Matrix34 &tm = m_pEntity->GetWorldTM();

#ifdef DEBUG_SOUNDPROXY	
	// this code is to detect lost sounds on Sound Proxies
	// enable for debugging
	TVecSoundID ToBeDeletedSoundID;

	IRenderer *pRenderer = gEnv->pRenderer;
	float colorwhite[4]={1,1,1,1};
	string sOutput= m_SoundsAttached.size() + " Proxy Sounds: \n";
#endif

	_smart_ptr<ISound> pSound = NULL;
	TAttachedSoundsVec::const_iterator const IterEnd(m_SoundsAttached.end());

	for (TAttachedSoundsVec::const_iterator Iter(m_SoundsAttached.begin()); Iter != IterEnd; ++Iter)
	{
		SAttachedSound const& slot = (*Iter);
		
		// In the "on-move-type" methods we use the sound pointer directly for performance reasons.
		pSound = slot.pSound;

		if (pSound)
		{
			SetSoundPosition(pSound, slot.bOffset?&slot.vOffset:0, &slot.vDirection, tm);
		}

#ifdef DEBUG_SOUNDPROXY	
		// this code is to detect lost sounds on Sound Proxies
		// enable for debugging
		if (slot.pSound && !slot.bStatic)
		{
			if (!slot.pSound->IsPlaying())
			{
				// found a sound which is supposed to be removed already
				ToBeDeletedSoundID.push_back(slot.nSoundID);
			}
		}

		sOutput += slot.pSound->GetName() + "\n";
#endif
	}

#ifdef DEBUG_SOUNDPROXY	
	// this code is to detect lost sounds on Sound Proxies
	// enable for debugging
	TVecSoundID::const_iterator IDItEnd(ToBeDeletedSoundID.end());

	for (TVecSoundID::const_iterator IDIt(ToBeDeletedSoundID.begin()); IDIt != IDItEnd; ++IDIt)
	{
		RemoveAttachedSound((*IDIt));
	}

	if (pRenderer)
	{
		pRenderer->DrawLabelEx(tm.GetTranslation(), 1.3f, colorwhite, true, false, sOutput.c_str());
	}
#endif
}


//////////////////////////////////////////////////////////////////////////
void CSoundProxy::OnLeaveNear(IEntity *pWho, IEntity *pArea)
{
	// not needed
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::OnMoveInside(IEntity *pWho, IEntity *pArea)
{
	IListener const* const pListener = gEnv->pSoundSystem->GetListener(gEnv->pSoundSystem->GetClosestActiveListener(pWho->GetWorldPos()));

	if (pListener && pListener->GetEntityID() == pWho->GetId())
		m_pEntity->SetPos(pListener->GetPosition());
	else
		m_pEntity->SetPos(pWho->GetWorldPos());

	_smart_ptr<ISound> pSound = NULL;
	TAttachedSoundsVec::const_iterator const IterEnd(m_SoundsAttached.end());

	for (TAttachedSoundsVec::const_iterator Iter(m_SoundsAttached.begin()); Iter != IterEnd; ++Iter)
	{
		// In the "on-move-type" methods we use the sound pointer directly for performance reasons.
		pSound = (*Iter).pSound;

		if (pSound)
		{
			pSound->GetInterfaceExtended()->SetVolume(1.0f);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::OnExclusiveMoveInside(IEntity *pWho, IEntity *pAreaLow, IEntity *pAreaHigh)
{
	IEntityAreaProxy const* const pAreaProxyLow	= static_cast<IEntityAreaProxy const* const>(pAreaLow->GetProxy(ENTITY_PROXY_AREA));
	IEntityAreaProxy * const pAreaProxyHigh     = static_cast<IEntityAreaProxy* const>(pAreaHigh->GetProxy(ENTITY_PROXY_AREA));

	if (pAreaProxyLow != NULL && pAreaProxyHigh != NULL)
	{
		Vec3 OnHighHull3d;

		IListener const* const pListener = gEnv->pSoundSystem->GetListener(gEnv->pSoundSystem->GetClosestActiveListener(pWho->GetWorldPos()));

		if (pListener && pListener->GetEntityID() == pWho->GetId())
		{
			bool const bInsideLow = pAreaProxyLow->CalcPointWithin(pListener->GetPosition());

			if (bInsideLow)
			{
				float const fDistSq = pAreaProxyHigh->ClosestPointOnHullDistSq(pListener->GetPosition(), OnHighHull3d);
				m_pEntity->SetPos(OnHighHull3d);

				// Now update the "spread" parameter of the outer sounds.
				_smart_ptr<ISound> pSound = NULL;
				TAttachedSoundsVec::const_iterator const IterEnd(m_SoundsAttached.end());

				for (TAttachedSoundsVec::const_iterator Iter(m_SoundsAttached.begin()); Iter != IterEnd; ++Iter)
				{
					// In the "on-move-type" methods we use the sound pointer directly for performance reasons.
					pSound = (*Iter).pSound;

					if (pSound)
					{
						float fVolume = 0.0f;

						if (m_fEffectRadius > 0.0f)
						{
							fVolume = 1.0f - min(1.0f, cry_sqrtf(fDistSq) / m_fEffectRadius);
						}

						if (pSound->GetFlags() & FLAG_SOUND_PARAM_SPREAD)
						{
							pSound->SetParam("spread", 1.0f - fVolume, false);
						}

						pSound->GetInterfaceExtended()->SetVolume(fVolume);
					}
				}
			}
		}
		else
		{
			bool const bInsideLow = pAreaProxyLow->CalcPointWithin(pWho->GetWorldPos());

			if (bInsideLow)
			{
				pAreaProxyHigh->ClosestPointOnHullDistSq(pWho->GetWorldPos(), OnHighHull3d);
				m_pEntity->SetPos(OnHighHull3d);
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::OnEnter(IEntity const* const pWho, IListener const* const pListener)
{
	if (pListener && pListener->GetEntityID() == pWho->GetId())
	{
		m_pEntity->SetPos(pListener->GetPosition());
	}
	else
	{
		if (pWho && (pWho->GetFlags() & ENTITY_FLAG_CAMERA_SOURCE))
		{
			m_pEntity->SetPos(pWho->GetWorldPos());
		}
	}

	_smart_ptr<ISound> pSound = NULL;
	TAttachedSoundsVec::const_iterator const IterEnd(m_SoundsAttached.end());

	for (TAttachedSoundsVec::const_iterator Iter(m_SoundsAttached.begin()); Iter != IterEnd; ++Iter)
	{
 		pSound = GetSound((*Iter).nSoundID);

		if (pSound)
		{
			// Set the "spread" parameter to 0 as we're now fully inside an area.
			if (pSound->GetFlags() & FLAG_SOUND_PARAM_SPREAD)
			{
				pSound->SetParam("spread", 0.0f, false);
			}

			// Set the listener angle to 0 and disable automation to prevent that parameter from jumping.
			if (pSound->GetFlagsExtended() & eSFE_LISTENER_ANGLE)
			{
				ptParamF32 oParamF32(0.0f);
				pSound->SetParam(spLISTENERANGLE, &oParamF32, true);
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::OnLeave(IEntity const* const pWho, IListener const* const pListener)
{
	_smart_ptr<ISound> pSound = NULL;
	TAttachedSoundsVec::const_iterator const IterEnd(m_SoundsAttached.end());

	for (TAttachedSoundsVec::const_iterator Iter(m_SoundsAttached.begin()); Iter != IterEnd; ++Iter)
	{
		pSound = GetSound((*Iter).nSoundID);

		if (pSound)
		{
			// Re-enable listener angle parameter automation so it's properly updating again.
			if (pSound->GetFlagsExtended() & eSFE_LISTENER_ANGLE)
			{
				ptParamF32 oParamF32(0.0f);
				pSound->SetParam(spLISTENERANGLE, &oParamF32, false);
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::OnAreaCrossing(IEntity const* const pWho, IListener const* const pListener)
{
	// Note: This proxy does not belong the the area we just either entered or left but to the same group regardless of priority.
	_smart_ptr<ISound> pSound = NULL;
	TAttachedSoundsVec::const_iterator const IterEnd(m_SoundsAttached.end());

	for (TAttachedSoundsVec::const_iterator Iter(m_SoundsAttached.begin()); Iter != IterEnd; ++Iter)
	{
		pSound = GetSound((*Iter).nSoundID);

		if (pSound)
		{
			// Reset the "spread" parameter to 0 as we just either entered or left an area.
			if (pSound->GetFlags() & FLAG_SOUND_PARAM_SPREAD)
			{
				pSound->SetParam("spread", 0.0f, false);
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::OnMoveNear(IEntity *pWho, IEntity *pArea, float fFade, float fDistanceSq)
{
	IEntityAreaProxy *pAreaProxy = (IEntityAreaProxy*)pArea->GetProxy(ENTITY_PROXY_AREA);
	Vec3 OnHull3d=Vec3(ZERO);
	float fDistSq = 0.0f;

	if (!pAreaProxy)
		return;

	IListener const* const pListener = gEnv->pSoundSystem->GetListener(gEnv->pSoundSystem->GetClosestActiveListener(pWho->GetWorldPos()));

	// TODO: Check use of cached data (requires implementation in "IEntityAreaProxy")
	if (pListener && pListener->GetEntityID() == pWho->GetId())
		fDistSq = pAreaProxy->CalcPointNearDistSq(pListener->GetPosition(), OnHull3d);
	else
		fDistSq = pAreaProxy->CalcPointNearDistSq(pWho->GetWorldPos(), OnHull3d);

	m_pEntity->SetPos(OnHull3d);

	_smart_ptr<ISound> pSound = NULL;
	TAttachedSoundsVec::const_iterator const IterEnd(m_SoundsAttached.end());

	for (TAttachedSoundsVec::const_iterator Iter(m_SoundsAttached.begin()); Iter != IterEnd; ++Iter)
	{
		// In the "on-move-type" methods we use the sound pointer directly for performance reasons.
		pSound = (*Iter).pSound;

		if (pSound)
		{
			float fVolume = 0.0f;

			if (m_fEffectRadius > 0.0f)
			{
				fVolume = (1.0f - (cry_sqrtf(fDistSq) * __fres(m_fEffectRadius) ) );
			}
			else
			{
				fVolume = 1.0f;
			}

			// update spread
			if (pSound->GetFlags() & FLAG_SOUND_PARAM_SPREAD)
			{
				pSound->SetParam("spread", 1.0f - fVolume, false);
			}

			fVolume = clamp(fVolume*fabsf(fFade), 0.0f, 1.0f);
			pSound->GetInterfaceExtended()->SetVolume(fVolume);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::OnHide( bool bHide )
{
	if (m_bHide != bHide)
	{
		m_bHide = bHide;

		_smart_ptr<ISound> pSound = NULL;
		TAttachedSoundsVec::const_reverse_iterator const IterRevEnd(m_SoundsAttached.rend());

		for (TAttachedSoundsVec::const_reverse_iterator IterRev(m_SoundsAttached.rbegin()); IterRev != IterRevEnd; ++IterRev)
		{
			pSound = GetSound((*IterRev).nSoundID);

			if (pSound)
			{
				if ((pSound->GetFlags() & FLAG_SOUND_LOOP) != 0)
				{
					gEnv->pSoundSystem->GetInterfaceDeprecated()->SetSoundActiveState(pSound, bHide?eSoundState_None:eSoundState_Active);
				}
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::ProcessEvent( SEntityEvent &event )
{
	switch(event.event) {
	case ENTITY_EVENT_XFORM:
		{
			OnMove();
			break;
		}
	case ENTITY_EVENT_ENTERAREA:
		{
			if ((m_pEntity->GetFlags() & ENTITY_FLAG_VOLUME_SOUND) != 0)
			{
				EntityId const nPlayerEntityID     = (EntityId) event.nParam[0]; // Player entity ID.
				IEntity const* const pPlayerEntity = gEnv->pEntitySystem->GetEntity(nPlayerEntityID);
				
				// TODO: always assume the player position to be the listener's position for now.
				// Fix this as soon as the AreaManager can handle multiple positions.
				OnEnter(pPlayerEntity, NULL);
			}

			break;
		}
	case ENTITY_EVENT_LEAVEAREA:
		{
			if ((m_pEntity->GetFlags() & ENTITY_FLAG_VOLUME_SOUND) != 0)
			{
				EntityId const nPlayerEntityID     = (EntityId) event.nParam[0]; // Player entity ID.
				IEntity const* const pPlayerEntity = gEnv->pEntitySystem->GetEntity(nPlayerEntityID);

				// TODO: always assume the player position to be the listener's position for now.
				// Fix this as soon as the AreaManager can handle multiple positions.
				OnLeave(pPlayerEntity, NULL);
			}

			break;
		}
	case ENTITY_EVENT_CROSS_AREA:
		{
			if ((m_pEntity->GetFlags() & ENTITY_FLAG_VOLUME_SOUND) != 0)
			{
				EntityId const nPlayerEntityID     = (EntityId) event.nParam[0]; // Player entity ID.
				IEntity const* const pPlayerEntity = gEnv->pEntitySystem->GetEntity(nPlayerEntityID);

				// TODO: always assume the player position to be the listener's position for now.
				// Fix this as soon as the AreaManager can handle multiple positions.
				OnAreaCrossing(pPlayerEntity, NULL);
			}

			break;
		}
	case ENTITY_EVENT_ENTERNEARAREA:
		{
			if (!(m_pEntity->GetFlags() & ENTITY_FLAG_VOLUME_SOUND))
				break;

			EntityId who = (EntityId) event.nParam[0]; // player

			IEntity* const pWho = gEnv->pEntitySystem->GetEntity(who);

			if (pWho)
			{
				IListener const* const pListener = gEnv->pSoundSystem->GetListener(gEnv->pSoundSystem->GetClosestActiveListener(pWho->GetWorldPos()));

				if (pListener && pListener->GetEntityID() == who || (pWho->GetFlags() & ENTITY_FLAG_CAMERA_SOURCE) != 0)
				{
					// the player is a registered listener, so essentially its the local actor
					EntityId area = (EntityId) event.nParam[2]; // AreaEntityID
					IEntity *pArea = gEnv->pEntitySystem->GetEntity(area);

					if (pArea)
					{
						OnMoveNear(pWho, pArea, event.fParam[0], event.fParam[1]);
					}
				}
			}
			
			break;
		}
	case ENTITY_EVENT_LEAVENEARAREA:
		{
			break;
		}
	case ENTITY_EVENT_MOVEINSIDEAREA:
		{
			if (!(m_pEntity->GetFlags() & ENTITY_FLAG_VOLUME_SOUND))
				break;

			EntityId who = (EntityId) event.nParam[0]; // player

			IEntity* const pWho = gEnv->pEntitySystem->GetEntity(who);

			if (pWho)
			{
				IListener const* const pListener = gEnv->pSoundSystem->GetListener(gEnv->pSoundSystem->GetClosestActiveListener(pWho->GetWorldPos()));

				if (pListener && pListener->GetEntityID() == who || (pWho->GetFlags() & ENTITY_FLAG_CAMERA_SOURCE) != 0)
				{
					// the player is a registered listener, so essentially its the local actor
					EntityId AreaID1 = (EntityId) event.nParam[2]; // AreaEntityID (low)
					EntityId AreaID2 = (EntityId) event.nParam[3]; // AreaEntityID (high)

					IEntity *pArea1 = gEnv->pEntitySystem->GetEntity(AreaID1);
					IEntity *pArea2 = gEnv->pEntitySystem->GetEntity(AreaID2);

					if (pArea1)
					{
						if(pArea2)
						{
							OnExclusiveMoveInside(pWho, pArea1, pArea2);
						}
						else
						{
							OnMoveInside(pWho, pArea1);
						}
					}
				}
			}
			
			break;
		}
	//case ENTITY_EVENT_EXCLUSIVEMOVEINSIDEAREA:
	//	{
	//		if (!(m_pEntity->GetFlags() & ENTITY_FLAG_VOLUME_SOUND))
	//			break;

	//		EntityId who = event.nParam[0]; // player

//			SListener *pListener = gEnv->pSoundSystem->GetListener(LISTENERID_STANDARD);
//			IEntity *pWho = gEnv->pEntitySystem->GetEntity(who);
//			bool bIsCamera = (pWho && pWho->GetClass() && strcmp(pWho->GetClass()->GetName(),"CameraSource") == 0 );

//			if (pListener && pListener->nEntityID==who || bIsCamera)
//			{
//				// the player is a registered listener, so essentially its the local actor
//				EntityId AreaHigh = event.nParam[2];	// AreaEntityID of higher area
//				EntityId AreaLow = event.nParam[3];		// AreaEntityID of lower area
//				
//				IEntity *pAreaHigh = gEnv->pEntitySystem->GetEntity(AreaHigh);
//				IEntity *pAreaLow = gEnv->pEntitySystem->GetEntity(AreaLow);

//				if (pWho && pAreaHigh && pAreaLow)
//					OnExclusiveMoveInside(pWho, pAreaHigh, pAreaLow);
//			}
	//		break;
	//	}
	case ENTITY_EVENT_MOVENEARAREA:
		{
			if (!(m_pEntity->GetFlags() & ENTITY_FLAG_VOLUME_SOUND))
				break;

			EntityId who = (EntityId) event.nParam[0]; // player

			IEntity* const pWho = gEnv->pEntitySystem->GetEntity(who);

			if (pWho)
			{
				IListener const* const pListener = gEnv->pSoundSystem->GetListener(gEnv->pSoundSystem->GetClosestActiveListener(pWho->GetWorldPos()));

				if (pListener && pListener->GetEntityID() == who || (pWho->GetFlags() & ENTITY_FLAG_CAMERA_SOURCE) != 0)
				{
					// the player is a registered listener, so essentially its the local actor
					EntityId area = (EntityId) event.nParam[2]; // AreaEntityID
					IEntity *pArea = gEnv->pEntitySystem->GetEntity(area);

					if (pArea)
					{
						OnMoveNear(pWho, pArea, event.fParam[0], event.fParam[1]);
					}
				}
			}
			
			break;
		}
	case ENTITY_EVENT_ANIM_EVENT:
		{
			if (!IsSoundAnimEventsHandledExternally())
			{
				const AnimEventInstance* pAnimEvent = reinterpret_cast<const AnimEventInstance*>(event.nParam[0]);
				ICharacterInstance* pCharacter = reinterpret_cast<ICharacterInstance*>(event.nParam[1]);
				const char* eventName = (pAnimEvent ? pAnimEvent->m_EventName : 0);
				if (eventName && stricmp(eventName, "sound") == 0)
				{
					Vec3 offset(ZERO);
					if (pAnimEvent->m_BonePathName && pAnimEvent->m_BonePathName[0])
					{
						ISkeletonPose* pSkeletonPose = (pCharacter ? pCharacter->GetISkeletonPose() : 0);

						int id = (pSkeletonPose ? pSkeletonPose->GetJointIDByName(pAnimEvent->m_BonePathName) : -1);
						if (pSkeletonPose && id >= 0)
						{
							QuatT boneQuat(pSkeletonPose->GetAbsJointByID(id));
							offset = boneQuat.t;
						}
					}

					int flags = FLAG_SOUND_DEFAULT_3D;
					if (strchr(pAnimEvent->m_CustomParameter, ':') == NULL)
						flags |= FLAG_SOUND_VOICE;
					PlaySound(pAnimEvent->m_CustomParameter, offset, FORWARD_DIRECTION, flags, 0, eSoundSemantic_Animation, 0, 0);
				}
			}
		}
		break;
	case ENTITY_EVENT_HIDE:
		OnHide(true);
		break;
	case ENTITY_EVENT_UNHIDE:
		OnHide(false);
		break;
	case ENTITY_EVENT_INVISIBLE:
		break;
	case ENTITY_EVENT_VISIBLE:
		break;
	case ENTITY_EVENT_INIT:
	case ENTITY_EVENT_DONE:
	case ENTITY_EVENT_PRE_SERIALIZE:
		StopAllSounds();
		break;
	}
}

//////////////////////////////////////////////////////////////////////////
bool CSoundProxy::GetSignature( TSerialize signature )
{
	// SoundProxy is not relevant to signature as it is always created again if needed
	return true;
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::Serialize( TSerialize ser )
{
#ifdef DEBUG_SOUNDPROXY
	//this is a good test to check if SoundProxies cleaned up their sounds correctly
	// activate for debug
	int nRefCount = 0;
	TAttachedSoundsVec::iterator ItEnd = m_SoundsAttached.end();
	for (TAttachedSoundsVec::iterator It = m_SoundsAttached.begin(); It != ItEnd; ++It)
	{
		SAttachedSound &slot = (*It);
		if (slot.pSound)
		{
			nRefCount = slot.pSound->AddRef() -1;
			slot.pSound->Release();
			assert (nRefCount > 1 || slot.pSound->IsPlaying());
		}
	}
#endif
}

//////////////////////////////////////////////////////////////////////////
ISound* const CSoundProxy::GetSound(tSoundID const nSoundId)
{
	_smart_ptr<ISound> pSound = NULL;

  if (nSoundId != INVALID_SOUNDID)
	{
		SAttachedSound* const pSoundSlot = FindAttachedSound(nSoundId);

		if (pSoundSlot)
		{
			pSound = gEnv->pSoundSystem->GetSound(pSoundSlot->nSoundID);

			if (!pSound)
			{
				pSoundSlot->nSoundID = INVALID_SOUNDID;
				pSoundSlot->pSound   = NULL;
			}
		}
	}

	return pSound;
}

//////////////////////////////////////////////////////////////////////////
CSoundProxy::SAttachedSound* const CSoundProxy::FindAttachedSound(tSoundID const nSoundId)
{
	TAttachedSoundsVec::const_iterator const IterEnd(m_SoundsAttached.end());

	for (TAttachedSoundsVec::iterator Iter(m_SoundsAttached.begin()); Iter != IterEnd; ++Iter)
	{
		SAttachedSound& slot = (*Iter);

		if (slot.nSoundID == nSoundId)
		{
			return (&slot);
		}
	}

	return NULL;
}

//////////////////////////////////////////////////////////////////////////
bool CSoundProxy::RemoveAttachedSound(tSoundID const nSoundId)
{
	_smart_ptr<ISound> pSound = NULL;
	TAttachedSoundsVec::const_iterator const IterEnd(m_SoundsAttached.end());

	for (TAttachedSoundsVec::iterator Iter(m_SoundsAttached.begin()); Iter != IterEnd; ++Iter)
	{
		SAttachedSound const& slot = (*Iter);

		if (slot.nSoundID == nSoundId)
		{
			pSound = gEnv->pSoundSystem->GetSound(slot.nSoundID);

			if (pSound)
			{
				pSound->RemoveEventListener(this);
			}

			m_SoundsAttached.erase(Iter);

			return true;
		}
	}

	return false;
}

//////////////////////////////////////////////////////////////////////////
bool CSoundProxy::AddAttachedSound( ISound * pSound, const bool bOffset, const Vec3 &vOffset, const Vec3 &vDirection )
{
	assert(pSound != NULL);

	if (!FindAttachedSound(pSound->GetId()))
	{
		SAttachedSound slot;
		slot.bOffset    = bOffset;
		slot.vOffset    = vOffset;
		slot.vDirection = vDirection;
		slot.pSound     = pSound;
		slot.nSoundID   = pSound->GetId();
		slot.bStatic    = false;

		m_SoundsAttached.push_back(slot);
		pSound->AddEventListener(this, m_pEntity->GetName());
		
		return true;
	}
	
	return false;
}

//////////////////////////////////////////////////////////////////////////
bool CSoundProxy::PlaySound( ISound *pSound, const Vec3 &vOffset,const Vec3 &vDirection,float fSoundScale,bool bLipSync )
{
	bool bOffset = !vOffset.IsZero() || !IsEquivalent(vDirection,FORWARD_DIRECTION);

	if (m_SoundsAttached.size() > TOO_MANY_SOUNDS)
	{
		// Forcefully stop and remove the first sound that was put in.
		TAttachedSoundsVec::const_iterator const IterEnd(m_SoundsAttached.end());

		for (TAttachedSoundsVec::const_iterator Iter(m_SoundsAttached.begin()); Iter != IterEnd; ++Iter)
		{
			SAttachedSound const& rSlotSound = (*Iter);

			if ((rSlotSound.pSound->GetFlags() & FLAG_SOUND_LOOP) == 0 && !rSlotSound.bStatic)
			{
				StopSound(rSlotSound.nSoundID, ESoundStopMode_AtOnce);

				break;
			}
		}
	}

	PrecacheHeadBone();

	const Matrix34& tm = m_pEntity->GetWorldTM();

	SetSoundPosition(pSound, bOffset?&vOffset:0, &vDirection, tm);	
	
	if (tm.GetTranslation() == Vec3Constants<float>::fVec3_Zero)
	{
		gEnv->pSoundSystem->Log(eSLT_Warning, "<Sound> Trying to play a sound at (0,0,0) position in the entity %s. Entity may not be initialized correctly! ",m_pEntity->GetEntityTextDescription() );
	}

	if (!m_bHide || (m_pEntity->GetFlags() & ENTITY_FLAG_UPDATE_HIDDEN))
	{
		AddAttachedSound(pSound, bOffset, vOffset, vDirection);
		if (bLipSync &&	(pSound->GetFlags() & FLAG_SOUND_VOICE))
		{
			// If voice is playing inform entity character (if present) about it to apply lip-sync.
			// actually facial sequence is triggered in OnSoundEvent SOUND_EVENT_ON_PLAYBACK_STARTED
			ICharacterInstance *pChar = m_pEntity->GetCharacter(0);
			if (pChar)
			{
				// when PLAYBACK is started, it will start facial sequence as well
				m_currentLipSyncId = pSound->GetId();
			}
		}
		pSound->Play(fSoundScale, true, true, this);
	}
	else
	{
		gEnv->pSoundSystem->Log(eSLT_Warning, "<Sound> Trying to play <%s> on %s which is hidden!", pSound->GetName(), m_pEntity->GetEntityTextDescription() );
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
tSoundID CSoundProxy::PlaySound( const char *sGroupAndSoundName, const Vec3 &vOffset,const Vec3 &vDirection, uint32 nSoundFlags, uint32 nSoundFlagsExtended, ESoundSemantic eSemantic)
{
	EntityId SkipEntIDs[1];
	SkipEntIDs[0] = m_pEntity->GetId();
	return PlaySound(sGroupAndSoundName, vOffset, vDirection, nSoundFlags, nSoundFlagsExtended, eSemantic, SkipEntIDs, 1);
}

//////////////////////////////////////////////////////////////////////////
tSoundID CSoundProxy::PlaySound( const char *sGroupAndSoundName, const Vec3 &vOffset,const Vec3 &vDirection, uint32 nSoundFlags, uint32 nSoundFlagsExtended, ESoundSemantic eSemantic, EntityId *pSkipEnts,int nSkipEnts)
{
	tSoundID ID = INVALID_SOUNDID;
	unsigned int nSoundFlagsLocal = nSoundFlags;

	char soundNameCopy[256];
	gEnv->pSoundSystem->HackFixupName(sGroupAndSoundName, soundNameCopy, 256);

	// Don't try to cull early if the flag is not set
	if (nSoundFlagsLocal & FLAG_SOUND_CULLING)
	{
		SSoundCacheInfo CullInfo;
		ESoundSystemErrorCode const nErrorCode = gEnv->pSoundSystem->GetCullingByCache(soundNameCopy, m_pEntity->GetWorldPos(), CullInfo);

		// no need to even create the sound
		if (nErrorCode == eSoundSystemErrorCode_None && CullInfo.bCanBeCulled)
			return ID;

		if (nErrorCode == eSoundSystemErrorCode_SoundCRCNotFoundInCache)
			nSoundFlagsLocal |= FLAG_SOUND_ADD_TO_CACHE;
	}
	
	_smart_ptr<ISound> pSound = gEnv->pSoundSystem->CreateSound(soundNameCopy, nSoundFlagsLocal, nSoundFlagsExtended);

	if (pSound)
	{
		pSound->SetSemantic(eSemantic);
		pSound->SetPhysicsToBeSkipObstruction(pSkipEnts, nSkipEnts);
		
		if (PlaySound(pSound, vOffset, vDirection, 1.0f))
			ID = pSound->GetId();
	}
	else
	{
		// sound failed to load, but still send the onDone event to Script or FG
		ESoundCallbackEvent myEvent = SOUND_EVENT_ON_STOP;
		OnSoundEvent( myEvent,NULL );
	}

	return ID;
}

//////////////////////////////////////////////////////////////////////////
tSoundID CSoundProxy::PlaySoundEx( const char *sGroupAndSoundName, const Vec3 &vOffset,const Vec3 &vDirection, uint32 nSoundFlags, uint32 nSoundFlagsExtended, float fVolume, float fMinRadius, float fMaxRadius, ESoundSemantic eSemantic)
{
	EntityId SkipEntIDs[1];
	SkipEntIDs[0] = m_pEntity->GetId();
	return PlaySoundEx(sGroupAndSoundName, vOffset, vDirection, nSoundFlags, nSoundFlagsExtended, fVolume, fMinRadius, fMaxRadius, eSemantic, SkipEntIDs, 1);
}

//////////////////////////////////////////////////////////////////////////
tSoundID CSoundProxy::PlaySoundEx( const char *sGroupAndSoundName, const Vec3 &vOffset,const Vec3 &vDirection, uint32 nSoundFlags, uint32 nSoundFlagsExtended, float fVolume, float fMinRadius, float fMaxRadius, ESoundSemantic eSemantic, EntityId *pSkipEnts, int nSkipEnts)
{
	tSoundID ID = INVALID_SOUNDID;
	SSoundCacheInfo CullInfo;
	unsigned int nSoundFlagsLocal = nSoundFlags;
	ESoundSystemErrorCode nErrorCode = gEnv->pSoundSystem->GetCullingByCache(sGroupAndSoundName, m_pEntity->GetWorldPos(), CullInfo);

	char soundNameCopy[256];
	gEnv->pSoundSystem->HackFixupName(sGroupAndSoundName, soundNameCopy, 256);

	// no need to even create the sound
	if (nErrorCode == eSoundSystemErrorCode_None && CullInfo.bCanBeCulled)
		return ID;

	if (nErrorCode == eSoundSystemErrorCode_SoundCRCNotFoundInCache)
		nSoundFlagsLocal |= FLAG_SOUND_ADD_TO_CACHE;

	_smart_ptr<ISound> pSound = gEnv->pSoundSystem->CreateSound(soundNameCopy, nSoundFlagsLocal, nSoundFlagsExtended);

	if (pSound)
	{
		pSound->SetSemantic(eSemantic);
		pSound->GetInterfaceExtended()->SetVolume(fVolume);
		
		if (fMinRadius>0 && fMaxRadius>0)
			pSound->GetInterfaceDeprecated()->SetMinMaxDistance(fMinRadius, fMaxRadius);
		
		pSound->SetPhysicsToBeSkipObstruction(pSkipEnts, nSkipEnts);		

		if (PlaySound(pSound, vOffset, vDirection, 1.0f))
			ID = pSound->GetId();
	}
	else
	{
		// sound failed to load, but still send the onDone event to Script or FG
		ESoundCallbackEvent myEvent = SOUND_EVENT_ON_STOP;
		OnSoundEvent( myEvent,NULL );
	}

	return ID;
}

//////////////////////////////////////////////////////////////////////////
bool CSoundProxy::SetStaticSound( tSoundID nSoundId, bool bStatic)
{
	bool bSuccess = false;
	SAttachedSound* const pSoundSlot = FindAttachedSound(nSoundId);

	if (pSoundSlot)
	{
		pSoundSlot->bStatic = bStatic;
		bSuccess = true;
	}

	return bSuccess;
}

//////////////////////////////////////////////////////////////////////////
bool CSoundProxy::GetStaticSound( const tSoundID nSoundId)
{
	bool bSuccess = false;
	SAttachedSound const* const pSoundSlot = FindAttachedSound(nSoundId);

	if (pSoundSlot)
	{
		bSuccess = pSoundSlot->bStatic;
	}

	return bSuccess;
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::StopSound(tSoundID const nSoundId, ESoundStopMode const eStopMode /* = ESoundStopMode_EventFade */, bool const bLoopedOnly /* = false */)
{
  if (nSoundId != INVALID_SOUNDID)
	{
		SAttachedSound* const pSoundSlot = FindAttachedSound(nSoundId);

		if (pSoundSlot && pSoundSlot->pSound)
		{
			bool bSoundStopped = false;

			if (!bLoopedOnly)
			{
				// We can simply call stop here as the user doesn't care about the event being a loop or not.
				// Even if the event is still loading the sound system will "stop" it as soon as the data arrived.
				pSoundSlot->pSound->Stop(eStopMode);
				bSoundStopped = true;
			}
			else
			{
				// Here we're supposed to stop the sound only if it's a looped event.
				if (pSoundSlot->pSound->IsLoaded())
				{
					if ((pSoundSlot->pSound->GetFlags() & FLAG_SOUND_LOOP) != 0)
					{
						pSoundSlot->pSound->Stop(eStopMode);
						bSoundStopped = true;
					}
				}
				else
				{					
					// We cannot determine if it's looped or not as it's not loaded yet.
					// Let's look at this sound again in its callback.
					stl::push_back_unique(m_anDelayedStopSoundIDs, nSoundId);
				}
			}

			if (bSoundStopped && !pSoundSlot->bStatic)
			{
				RemoveAttachedSound(nSoundId);
			}
		}
		else
		{
			// This should not happen but lets try to stop the sound even if its not known to the proxy.
			_smart_ptr<ISound> const pSound = gEnv->pSoundSystem->GetSound(nSoundId);

			if (pSound)
			{
				pSound->Stop(eStopMode);
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::PauseSound( tSoundID nSoundId, bool bPause )
{
	if (nSoundId != INVALID_SOUNDID)
	{
		SAttachedSound const* const pSoundSlot = FindAttachedSound(nSoundId);

		if (pSoundSlot && pSoundSlot->pSound)
		{
			pSoundSlot->pSound->SetPaused(bPause);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::StopAllSounds()
{
	TAttachedSoundsVec::reverse_iterator const IterEnd(m_SoundsAttached.rend());

	for (TAttachedSoundsVec::reverse_iterator Iter(m_SoundsAttached.rbegin()); Iter != IterEnd; ++Iter)
	{
		SAttachedSound& rSlot = (*Iter);
		rSlot.bStatic = false;
		rSlot.pSound->RemoveEventListener(this);
		rSlot.pSound->Stop(ESoundStopMode_AtOnce);
	}

	stl::free_container(m_SoundsAttached);
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::SetSoundPos( tSoundID nSoundId,const Vec3 &vOffset )
{
	SAttachedSound* const pSoundSlot = FindAttachedSound(nSoundId);

	if (pSoundSlot)
	{
		pSoundSlot->vOffset = vOffset;
		pSoundSlot->bOffset = !pSoundSlot->vOffset.IsZero() || !IsEquivalent(pSoundSlot->vDirection,FORWARD_DIRECTION);
	}
}

//////////////////////////////////////////////////////////////////////////
Vec3 CSoundProxy::GetSoundPos( tSoundID nSoundId )
{
	SAttachedSound const* const pSoundSlot = FindAttachedSound(nSoundId);

	if (pSoundSlot)
	{
		return pSoundSlot->vOffset;
	}

	return Vec3(0,0,0);
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::SetSoundDirection( tSoundID nSoundId,const Vec3 &dir )
{
	SAttachedSound* const pSoundSlot = FindAttachedSound(nSoundId);

	if (pSoundSlot)
	{
		pSoundSlot->vDirection = dir;
		pSoundSlot->bOffset    = !pSoundSlot->vOffset.IsZero() || !IsEquivalent(pSoundSlot->vDirection,FORWARD_DIRECTION);
	}
}

//////////////////////////////////////////////////////////////////////////
Vec3 CSoundProxy::GetSoundDirection( tSoundID nSoundId )
{
	SAttachedSound const* const pSoundSlot = FindAttachedSound(nSoundId);

	if (pSoundSlot)
	{
		return pSoundSlot->vDirection;
	}
	
	return FORWARD_DIRECTION;
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::OnSoundEvent( ESoundCallbackEvent event,ISound *pSound )
{
	// Use a smart pointer while working on the instance.
	_smart_ptr<ISound> const pLocalSound = pSound;
	tSoundID const nSoundID = (pLocalSound != NULL) ? pLocalSound->GetId() : INVALID_SOUNDID;

	switch (event)
	{
	case SOUND_EVENT_ON_PLAYBACK_STARTED:
		{
			if (nSoundID != INVALID_SOUNDID && nSoundID == m_currentLipSyncId)
			{
				ICharacterInstance* const pChar = m_pEntity ? m_pEntity->GetCharacter(0) : NULL;

				if (pChar)
				{
					pChar->LipSyncWithSound(m_currentLipSyncId);
				}
			}

			break;
		}
	case SOUND_EVENT_ON_STOP:
		{
			if (nSoundID != INVALID_SOUNDID)
			{
				if (nSoundID == m_currentLipSyncId)
				{
					if (m_pEntity)
					{
						ICharacterInstance* const pChar = m_pEntity->GetCharacter(0);

						if (pChar)
						{
							pChar->LipSyncWithSound(m_currentLipSyncId, true);
						}
					}

					m_currentLipSyncId = INVALID_SOUNDID;
				}				
				
				// Can be NULL if the SoundSystem is disabled or the sound wasn't found.
				SAttachedSound const* const pSoundSlot = FindAttachedSound(nSoundID);

				assert(pSoundSlot);

				if (pSoundSlot)
				{
					if (!pSoundSlot->bStatic)
					{
						pLocalSound->RemoveEventListener(this);
						RemoveAttachedSound(nSoundID);
						// pSound may be invalid from now on
					}
				}
			}

			// send event to entity (lua or FG node)
			if (m_pEntity)
			{
				SEntityEvent DoneEvent;
				DoneEvent.event = ENTITY_EVENT_SOUND_DONE;
				DoneEvent.nParam[0] = m_pEntity->GetId();
				m_pEntity->SendEvent( DoneEvent );
			}

			break;
		}
	case SOUND_EVENT_ON_LOAD_FAILED:
		{
			// Remove sound from set.
			if (nSoundID != INVALID_SOUNDID)
			{
				pLocalSound->RemoveEventListener(this);
				RemoveAttachedSound(nSoundID);
			}

			break;
		}
	case SOUND_EVENT_ON_INFO:
		{
			if (nSoundID != INVALID_SOUNDID && !m_anDelayedStopSoundIDs.empty())
			{
				bool bFound = false;
				TVecSoundID::const_iterator const IterEnd(m_anDelayedStopSoundIDs.end());

				for (TVecSoundID::iterator Iter(m_anDelayedStopSoundIDs.begin()); Iter != IterEnd; ++Iter)
				{
					if ((*Iter) == nSoundID)
					{
						bFound = true;

						// This sound event was still loading at the time Stop() was called on it.
						// Also we're supposed to only stop it if it's a loop.
						if ((pLocalSound->GetFlags() & FLAG_SOUND_LOOP) != 0)
						{
							// As stop was called right after play (event was still loading),
							// we assume the user didn't want it to play anyway so we stop it without fade out.
							pLocalSound->Stop(ESoundStopMode_AtOnce); // Calls back with SOUND_EVENT_ON_STOP which cleans up.
						}

						m_anDelayedStopSoundIDs.erase(Iter);
						break;
					}
				}

				assert(bFound);
			}

			break;
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSoundProxy::CheckVisibilityForTailName(const float fLength, const float fDistanceToRecalculate)
{
	if (m_pEntity)
	{
		if (m_pEntity->GetWorldPos().GetSquaredDistance(m_vLastVisibilityPos) > fDistanceToRecalculate*fDistanceToRecalculate)
		{
			m_vLastVisibilityPos = m_pEntity->GetWorldPos();

			Quat const qDirection = m_pEntity->GetWorldRotation().GetNormalized();
			Vec3 const vDirection = qDirection * Vec3(0,0,fLength);

			IPhysicalEntity* pSkipEnts;
			int nSkipEnts = 0;

			IPhysicalEntity* const pPhysicalEntity = m_pEntity->GetPhysics();
			if (pPhysicalEntity)
			{
				pSkipEnts = pPhysicalEntity;
				++nSkipEnts;
			}

			EntityId const nEntityID = m_pEntity->GetId();
			gEnv->pPhysicalWorld->RayWorldIntersection(m_vLastVisibilityPos, vDirection, ent_static|ent_terrain, rwi_pierceability0|rwi_queue, NULL, 5, &pSkipEnts, nSkipEnts, (void*)nEntityID, PHYS_FOREIGN_ID_SOUND_PROXY_OBSTRUCTION);
		}

		if (CVar::pDrawSoundProxyZRay->GetIVal() != 0)
		{
			IRenderAuxGeom* pAuxGeom( gEnv->pRenderer->GetIRenderAuxGeom() );
			pAuxGeom->SetRenderFlags( e_Def3DPublicRenderflags );

			ColorB red(255,0,0,255);
			ColorB green(0,255,0,255);
			if (m_sTailname == SOUND_INDOOR_TAILNAME)
				pAuxGeom->DrawLine(m_vLastVisibilityPos, red, m_vLastVisibilityPos+Vec3(0,0,fLength), red, 3.0f);
			else
				pAuxGeom->DrawLine(m_vLastVisibilityPos, green, m_vLastVisibilityPos+Vec3(0,0,fLength), green, 3.0f);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
IListener const* const CSoundProxy::GetListenerClosestToPlayer(EntityId const nPlayerEntityID) const
{
	IEntity const* const pPlayerEntity = gEnv->pEntitySystem->GetEntity(nPlayerEntityID);

	if (pPlayerEntity)
	{
		return gEnv->pSoundSystem->GetListener(gEnv->pSoundSystem->GetClosestActiveListener(pPlayerEntity->GetWorldPos()));
	}

	return NULL;
}

#include UNIQUE_VIRTUAL_WRAPPER(IEntitySoundProxy)
